#include <math.h>

#pragma once

namespace AGE
{
    template <typename T>

    /**
     * @brief 2D vector implementation
     * @author Andrew Caci
     * @date October 18, 2009
     *
     * Provides basic functionality for vector math
     */
    class Vector2D
    {

    public: 

        /**
         * @brief Default constructor. Sets the vector to {0,0}
         */
        Vector2D<T>() : m_X((T)0), m_Y((T)0) 
        {
        }

        /**
         * @brief Overloaded constructor. Sets the vector to the specified values.
         * 
         * @param iX The x component
         * @param iY The y component
         */
        Vector2D<T>(T iX, T iY)
        {
            Set(iX, iY);
        }

        /**
         * @brief Copy constructor
         */
        template <typename S>
        Vector2D<T> (const Vector2D<S>& iOther)
        {
            m_X = (T)iOther.GetX();
            m_Y = (T)iOther.GetY();
        }
           
        /**
         * @brief Sets the x and y components of the vector
         *
         * @param iX The x component
         * @param iY The y component
         */
        inline void Set(T iX, T iY)
        {
            m_X = (T)iX;
            m_Y = (T)iY;
        }

        /**
         * @brief Sets the x and y component of this vector to the other
         *
         * @param iVector The vector containing the x and y components
         */
        inline void Set(const Vector2D<T>& iVector)
        {
            m_X = (T)iVector.GetX();
            m_Y = (T)iVector.GetY();
        }

        /**
         * @brief Sets the x component
         *
         * @iX The x component
         */
        inline void SetX(T iX)
        {
            m_X = (T)iX;
        }

        /**
         * @brief Sets the y component
         *
         * @iX The y component
         */        
        inline void SetY(T iY)
        {
            m_Y = (T)iY;
        }

        /**
         * @brief Gets the x component
         * 
         * @return The x component
         */
        inline T GetX() const
        {
            return m_X;
        }

        /**
         * @brief Gets the y component
         * 
         * @return The y component
         */
        inline T GetY() const
        {
            return m_Y;
        }       

        /**
         * @brief Rotates this vector around {0,0} counter clockwise
         * 
         * @param iAngleDeg The rotation angle in degrees
         */
        inline void Rotate(float iAngleDeg)
        {
            const float wAngleRad = iAngleDeg * cDegToRad;

            //Rotate
            m_X = (T)(m_X * cos(wAngleRad)) - (m_Y * sin(wAngleRad));
            m_Y = (T)(m_X * sin(wAngleRad)) + (m_Y * cos(wAngleRad));  
        }

        /**
         * @brief Rotates this vector around another vector counter clockwise
         * 
         * @param iVector The vector to rotate around
         * @param iAngleDeg The rotation angle in degrees
         */
        void rotate(const Vector2D<T>& iVector, float iAngleDeg)
        {
            const T wDeltaX = iVector.GetX();
            const T wDeltaY = iVector.GetY();

            //Center to origin       
            m_X -= wDeltaX;
            m_Y -= wDeltaY;

            rotate(iAngleDeg);

            //Put it back to original position
            m_X += wDeltaX;
            m_Y += wDeltaY;
        }

        /**
         * @brief Translates this vector 
         *
         * @param iDeltaPosition The vector containing the x and y components to translate
         */
        inline void Translate(const Vector2D<T>& iDeltaPosition)
        {
            m_X += iDeltaPosition.getX();
            m_Y += iDeltaPosition.getY();
        }

        inline Vector2D<T>& operator +=(const Vector2D<T>& iOther)
        {
            m_X += iOther.m_X;
            m_Y += iOther.m_Y;
            return (*this);
        }
        
        inline Vector2D<T>& operator -=(const Vector2D<T>& iOther)
        {
            m_X -= iOther.m_X;
            m_Y -= iOther.m_Y;
            return (*this);
        }
        
        inline Vector2D<T>& operator *=(T iValue)
        {
            m_X *= iValue;
            m_Y *= iValue;
            return (*this);
        }
        
        inline Vector2D<T>& operator /=(T iValue)
        {
            const float wMultipler = 1.0f / iValue;
            m_X = T(m_X * wMultipler);
            m_Y = T(m_Y * wMultipler);
            return (*this);
        }      
    
        inline Vector2D<T>& operator *=(const Vector2D<T>& iOther)
        {
            m_X *= iOther.m_X;
            m_Y *= iOther.m_Y;
            return (*this);
        }
        
        inline Vector2D<T>& operator /=(const Vector2D<T>& iOther )
        {
            const float wMultiplerX = 1.0f / iOther.m_X;
            const float wMultiplerY = 1.0f / iOther.m_Y;
            m_X = T(m_X * wMultiplerX);
            m_Y = T(m_Y * wMultiplerY);
            return (*this);
        }     

        inline Vector2D<T> operator -(void) const
        {
            return (Vector2D<T>(-m_X, -m_Y));
        }
        
        inline Vector2D<T> operator +(const Vector2D<T>& iOther) const
        {
            return (Vector2D<T>((T)m_X + iOther.m_X, (T)m_Y + iOther.m_Y));
        }
        
        inline Vector2D<T> operator -(const Vector2D<T>& iOther) const
        {
            return (Vector2D<T>((T)m_X - iOther.m_X, (T)m_Y - iOther.m_Y));
        }
        
        inline Vector2D<T> operator *(T iValue) const
        {
            return (Vector2D<T>((T)m_X * iValue, (T)m_Y * iValue));
        }
        
        inline Vector2D<T> operator /(T iValue) const
        {
            const float wMultiplier = (1.0f / iValue);
            return (Vector2D<T>(T(m_X * wMultiplier), T(m_Y * wMultiplier)));
        }

         inline Vector2D<T> operator *(const Vector2D<T>& iOther) const
        {
            return (Vector2D<T>((T)m_X * iOther.GetX(), (T)m_Y * iOther.gGetY()));
        }
        
        inline Vector2D<T> operator /(const Vector2D<T>& iOther) const
        {
            const float wMultiplierX = (1.0f / iOther.GetX());
            const float wMultiplierY = (1.0f / iOther.GetY());
            return (Vector2D<T>(T(m_X * wMultiplierX), T(m_Y * wMultiplierY)));
        }
        

        inline bool operator ==(const Vector2D<T>& iOther) const
        {
            return ((m_X == iOther.m_X) && (m_Y == iOther.m_Y));
        }
        
        inline bool operator !=(const Vector2D<T>& iOther) const
        {
            return ((m_X != iOther.m_X) || (m_Y != iOther.m_Y));
        }
  
        /**
         * @brief Returns the magnitude of this vector
         *
         * @return The length of this vector
         */
        inline float Magnitude() const
        {
            return sqrt(( (m_X * m_X) + (m_Y * m_Y) ));
        }

        /**
         * @brief Returns the cross product of this vector
         *
         * @return A vector which is perpindicular to this vector
         */
        inline float CrossProduct(const Vector2D& iOther) const
        {
            return (m_X * iOther.m_Y) - (m_Y * iOther.m_X);
        }

        /**
         * @brief Returns the dot product between these vectors
         *
         * @return The angular relationship between the 2 vectors
         */
        inline float DotProduct(const Vector2D& iOther) const
        {
            return ((m_X * iOther.m_X) + (m_Y * iOther.m_Y));
        }

        /**
         * @brief Normalises this vector (makes the x and y component be within 0 and 1
         */
        inline void Normalise()
        {
            float wMagnitude = Magnitude();


            m_X /= wMagnitude;
            m_Y /= wMagnitude;
        }

     protected:
        T m_X;
        T m_Y;
    };
}
